Skip to content

chore(phpstan): analyse statique niveau 5 sans baseline (463 → 0) - #339

Merged
horacioskrp merged 12 commits into
developfrom
chore/phpstan-debt-1
Nov 13, 2026
Merged

horacioskrp merged 12 commits into
developfrom
chore/phpstan-debt-1

Conversation

@horacioskrp

Copy link
Copy Markdown
Collaborator

Objectif

Intègre PHPStan / Larastan au projet et résorbe l'intégralité de la dette d'analyse statique : niveau 5 passe désormais sans aucune baseline (le fichier phpstan-baseline.neon est supprimé).

Parcours

Étape Erreurs
Ajout PHPStan niveau 5 463
Typage générique des relations Eloquent 250
…lots successifs de corrections… 162 → 124 → 87 → 56 → 47
Final (sans baseline) 0

Démarche

Chaque erreur a été traitée à la source : typage correct quand c'est possible, jamais de suppression de confort. Les rares faux positifs irréductibles du framework sont documentés inline (une seule occurrence restante).

Typage honnête

  • Portail API (ApiController, AuthController, ChildrenController) : le principal authentifié est Student|Guardian (gardes Sanctum), annoté comme tel.
  • Lignes d'agrégat (Enrollment, StudentStats, Dashboard, Payroll) : résultats selectRaw/jointures typés par leur forme réelle.
  • OfficialExam : @property-read pour les alias withCount.
  • User / Student : relation Spatie roles et pivot guardian_student typés.
  • TimetableController : closure et forme de la grille typées.

Vrais bugs corrigés au passage

  • CashAccountController : la contrainte INCOME du withSum était ignorée (closure en 3ᵉ argument positionnel) → total_income comptait toutes les transactions.
  • ArchiveController : le libellé d'archive d'un élève affichait un nom vide (Student n'a pas de colonne name) → compose prénom + nom.
  • Migration evaluations : ->after() chaîné sur la définition de clé étrangère (ignoré) → réordonné.
  • UniqueMatricule : comparaison string === int toujours fausse (UUID) → type corrigé.
  • BackupService : échec de tempnam() (string|false) non géré.

Code mort retiré

Arms de match/ternaires rendus inatteignables par les enums et colonnes NOT NULL, offsets ?? défaut toujours présents, constante TYPES morte, guard method_exists redondant.

Infra

  • Job CI static-analysis (PHP 8.3) exécutant composer analyse.
  • Script Composer analyse.

Vérification

  • PHPStan niveau 5 : [OK] No errors sans baseline.
  • Tests : 502 passés. Les 3 échecs restants (SeederHardeningTest) sont préexistants et sans rapport (seeders appelés hors contexte console → $this->command nul), vérifié par comparaison sur le code d'origine.
  • Pint : pass.

- CashAccountController: la contrainte INCOME du withSum était ignorée
  (closure passée en 3e arg positionnel) → syntaxe tableau, total_income
  ne compte plus que les transactions INCOME.
- BackupService: garde l'échec de tempnam() (string|false) et retire le
  `! empty($chunk)` mort sur array_chunk.
- BulletinRenderer / PayrollService: retire le `!== null` redondant après
  isset().
- FrenchNumberSpeller: remplace le fallback hors-plage UNITS[$n] par une
  LogicException explicite.
- StudentTestSeeder: resolveAcademicYear() ne retourne jamais null, type
  de retour non-nullable + condition d'inscription simplifiée.
- HasMatricule / ArchiveController: type `self` sur la closure creating,
  retrait de la constante TYPES morte.
- Baseline régénérée : 47 faux positifs résiduels (alias de requêtes
  selectRaw/withCount, relations Spatie, auth multi-modèle Sanctum).
…line (47 → 0)

Niveau 5 désormais vert SANS baseline : le fichier phpstan-baseline.neon est
supprimé et son include retiré de phpstan.neon.

Typage honnête (pas de suppression) :
- Portail API (ApiController, AuthController, ChildrenController) : le principal
  authentifié est Student|Guardian (gardes Sanctum), annoté comme tel → instanceof,
  collect() et currentAccessToken() se résolvent.
- Lignes d'agrégat (Enrollment/StudentStats/Dashboard/Payroll) : résultats
  selectRaw/jointures typés via variables intermédiaires (object{...}).
- OfficialExam : @property-read pour les alias withCount.
- User / Student : relation Spatie roles et pivot guardian_student typés.
- TimetableController : closure $range et forme de la grille typées.

Vrais bugs corrigés au passage :
- ArchiveController : le libellé d'archive d'un élève affichait un nom vide
  (Student n'a pas de colonne « name ») → compose prénom + nom.
- Migration evaluations : ->after() était chaîné sur la définition de clé
  étrangère (ignoré) → réordonné pour positionner la colonne.
- Migration matricules : retrait d'un ->unique()->index() sans effet.

Code mort retiré (enums/colonnes NOT NULL → arms inatteignables) :
CashAccount::typeLabel, HasMatricule::method_exists.

DocumentRenderer : genre/date de naissance lus via getAttribute() (mixed) car
$student peut être un modèle NON persisté (aperçu/test) dont les colonnes NOT
NULL sont nulles — les gardes défensives sont conservées.

StatisticsService : unique suppression inline documentée (count() sur GROUP BY
+ HAVING ; un count() SQL compterait par groupe, pas les groupes).
…ederHardeningTest)

Un commit précédent avait retiré les `?->` sur $this->command, jugés superflus
par PHPStan (Seeder::$command est typé non-nul par le framework). Mais la
propriété vaut null quand run() est appelé directement, hors `db:seed` — ce que
font les seeders invoqués programmatiquement et SeederHardeningTest, d'où un
« Call to a member function warn()/info() on null » en CI.

- Rétablit `$this->command?->` dans les 5 seeders (comportement identique quand
  la commande console est présente, sûr quand elle est absente).
- phpstan.neon : règle ignoreErrors ciblée et documentée pour le faux positif
  nullsafe.neverNull sur Illuminate\Console\Command dans database/seeders.

Suite complète : 505 passants, 0 échec. PHPStan niveau 5 : vert.
@horacioskrp
horacioskrp merged commit 2600018 into develop Nov 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant